<?php
/**
 * PageMaster is a PHP templating engine with template inheritance
 * builtin from the core
 * Project details available at: http://code.google.com/p/pagemaster
 */
class PageMaster
{
    protected $TemplateExtensionClasses = array();
    protected $ViewData = array();

    /**
     * Processes and Renders a view file
     *
     * @param <string> $View View file to render, path relative to executing script
     * @param <mixed> $ViewData Associative array of data to make available to the View
     */
    public function Render($View, $ViewData = array())
    {
        $view = new View($View, array_merge($this->ViewData,$ViewData), $this->TemplateExtensionClasses);
    }

    /**
     * Register a given class's functions for use in the view
     * templates. Functions will be available as $this->function(..)
     *
     * @param <string> $class Name of the class which contains the template functions
     */
    public function AddFunctionClass($class)
    {
        $this->TemplateExtensionClasses[] = $class;
    }

    /**
     * Add View Data to the View engine before rendering the view. Useful for adding data to the view
     * as it becomes available.
     *
     * @param <string> $key Desired key for access through $ViewData in the View
     * @param <mixed> $value
     * @return <mixed> input $value
     */
    public function AddViewData($key,$value)
    {
        return $this->ViewData[$key] = $value;
    }
    
    public function __set($key,$value)
    {
        return $this->AddViewData($key,$value);
    }
}

class View
{
    private $_FunctionClasses;
    private $_ProcessMasterFile;
    private static $_Contents;
    private $_ViewData;
    private $_PostObCallbackHolderName;

    public function __construct($viewfile, $ViewData, $FunctionClasses)
    {
        $this->_FunctionClasses = $FunctionClasses;
        $this->_ViewData = $ViewData;
        include($viewfile);
        while($this->_ProcessMasterFile)
        {
            $master = $this->_ProcessMasterFile;
            $this->_ProcessMasterFile = "";
            include($master);
        }
    }

    public function __call($method,$params)
    {
        foreach( $this->_FunctionClasses as $i=>$class)
        {
            if( method_exists($class, $method) )
            {
                array_unshift($params,$this);
                return call_user_func_array(array($class,$method),$params);
            }
        }
        throw new Exception(
            sprintf("Template function %s not found. Classes searched: %s",
                $method, implode(',', $this->_FunctionClasses)
                )
            );
    }

    protected function extend($master)
    {
        $ViewData = $this->_ViewData;
        $this->_ProcessMasterFile=$master;
    }

    protected function partial($partial,$PartialData = array())
    {
        $ViewData = array_merge($this->_ViewData,$PartialData);
        include($partial);
    }

    protected function content($name, $enableNesting=true, $callback=null)
    {
        $func_fill_contents = 'View::_fillContents("'.$name.'",$content);';
        if(null !== $callback) { $func_fill_contents.=$callback; }

        $func_drop_contents = '';
        ob_start( create_function('$content',
                (empty(self::$_Contents[$name]) || $enableNesting)?$func_fill_contents:$func_fill_contents) );
    }
    protected function endContent()
    {
        ob_end_clean();
        if(null != $this->_PostObCallbackHolderName)
        {
            $this->contentHolder($this->_PostObCallbackHolderName);
            $this->_PostObCallback = null;
        }
    }
    public static function _fillContents($name,$content)
    {
        self::$_Contents[$name] = $content;
    }

    public function contentHolder($name,$hasDefaultContent=false)
    {
        if(!empty(self::$_Contents[$name]))
        {
            echo self::$_Contents[$name];
             if($hasDefaultContent)
             {
                $func_drop_contents = '';
                ob_start( create_function('$content',$func_drop_contents) );
             }
        }
        else if($hasDefaultContent)
        {
            $this->_PostObCallbackHolderName = $name;
            $this->content($name);
        }
    }

    public function resolve($path)
    {
        echo str_replace('//','/',str_replace('~',dirname($_SERVER['SCRIPT_NAME']."/"),$path));
    }
}
